#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "Translation.h"
#include "../BMC.h"
#include "BMCIStringSynth.h"
#include "../BMCIR.h"
#include "../Context/ExeContext.h"
#include "../../List.h"
#include "../../JRYNES.h"
#include "../Scanning/Scanner.h"

extern EmulationSettings Settings;

//Instruction mnemonic Instruction String Synthesizer
//Since the opcodes also identifies which addressing mode is being
//used, the only thing these routines should do is return
//the mnemonic text associated.
static const char* adc(){const char *Text = "adc"; return Text;}
static const char* and(){const char *Text = "and"; return Text;}
static const char* asl(){const char *Text = "asl"; return Text;}
static const char* bcc(){const char *Text = "bcc"; return Text;}
static const char* bcs(){const char *Text = "bcs"; return Text;}
static const char* beq(){const char *Text = "beq"; return Text;}
static const char* bit(){const char *Text = "bit"; return Text;}
static const char* bmi(){const char *Text = "bmi"; return Text;}
static const char* bne(){const char *Text = "bne"; return Text;}
static const char* bpl(){const char *Text = "bpl"; return Text;}
static const char* brk(){const char *Text = "brk"; return Text;}
static const char* bvc(){const char *Text = "bvc"; return Text;}
static const char* bvs(){const char *Text = "bvs"; return Text;}
static const char* clc(){const char *Text = "clc"; return Text;}
static const char* cld(){const char *Text = "cld"; return Text;}
static const char* cli(){const char *Text = "cli"; return Text;}
static const char* clv(){const char *Text = "clv"; return Text;}
static const char* cmp(){const char *Text = "cmp"; return Text;}
static const char* cpx(){const char *Text = "cpx"; return Text;}
static const char* cpy(){const char *Text = "cpy"; return Text;}
static const char* dec(){const char *Text = "dec"; return Text;}
static const char* dex(){const char *Text = "dex"; return Text;}
static const char* dey(){const char *Text = "dey"; return Text;}
static const char* eor(){const char *Text = "eor"; return Text;}
static const char* inc(){const char *Text = "inc"; return Text;}
static const char* inx(){const char *Text = "inx"; return Text;}
static const char* iny(){const char *Text = "iny"; return Text;}
static const char* jmp(){const char *Text = "jmp"; return Text;}
static const char* jsr(){const char *Text = "jsr"; return Text;}
static const char* lda(){const char *Text = "lda"; return Text;}
static const char* ldx(){const char *Text = "ldx"; return Text;}
static const char* ldy(){const char *Text = "ldy"; return Text;}
static const char* lsr(){const char *Text = "lsr"; return Text;}
static const char* nop(){const char *Text = "nop"; return Text;}
static const char* ora(){const char *Text = "ora"; return Text;}
static const char* pha(){const char *Text = "pha"; return Text;}
static const char* php(){const char *Text = "php"; return Text;}
static const char* pla(){const char *Text = "pla"; return Text;}
static const char* plp(){const char *Text = "plp"; return Text;}
static const char* rol(){const char *Text = "rol"; return Text;}
static const char* ror(){const char *Text = "ror"; return Text;}
static const char* rti(){const char *Text = "rti"; return Text;}
static const char* rts(){const char *Text = "rts"; return Text;}
static const char* sbc(){const char *Text = "sbc"; return Text;}
static const char* sec(){const char *Text = "sec"; return Text;}
static const char* sed(){const char *Text = "sed"; return Text;}
static const char* sei(){const char *Text = "sei"; return Text;}
static const char* sta(){const char *Text = "sta"; return Text;}
static const char* stx(){const char *Text = "stx"; return Text;}
static const char* sty(){const char *Text = "sty"; return Text;}
static const char* tax(){const char *Text = "tax"; return Text;}
static const char* tay(){const char *Text = "tay"; return Text;}
static const char* tsx(){const char *Text = "tsx"; return Text;}
static const char* txa(){const char *Text = "txa"; return Text;}
static const char* txs(){const char *Text = "txs"; return Text;}
static const char* tya(){const char *Text = "tya"; return Text;}
static const char* aso(){const char *Text = "aso"; return Text;}
static const char* dop(){const char *Text = "dop"; return Text;}
static const char* top(){const char *Text = "top"; return Text;}
static const char* jam(){const char *Text = "jam"; return Text;} 

const BISSynthData IRSynthInfo[256] = {
    #include "../../6502_OpcodeMap.h"
};

typedef struct JumpTableData {
    uint16_t StartingAddress;
    uint32_t Size;
} JumpTableData;

inline static JumpTableData* JTD_New(uint16_t StartingAddress, uint32_t Size){
    JumpTableData* New;
    
    New = malloc(sizeof(JumpTableData));
    New->Size = Size;
    New->StartingAddress = StartingAddress;
    
    return New;
}

static void JTD_Destroyer(void* Element){
    free((JumpTableData*)Element);
}

static inline uint32_t BIS_IsAddrModeStatic(uint32_t AddrMode){
    return AddrMode <= 2 ? 1 : 0;
}

/*static char InstructionBuffer [0x4000];
static char JumpTable [0x2000];
static char ContextInfo [0x2000];
static char Scrap [0x1000];*/

char* BIS_Synthesize(BMC* Block, uint32_t Flags, ExeContext *InitialContext){
    char* InstructionBuffer, *JumpTable, *ContextInfo;
    char* DeclBuffer;
    char Scrap[0x1000];
    uint8_t InstructionsUsed[0x100];
    uint16_t BufferSize;
    uint32_t i;
    List *JumpTableList;
    JumpTableData *JTData;
    
#ifdef __linux__
    BufferSize = getpagesize() * 2;
#else
    BufferSize = 8192;
#endif
    
    //This function should have at least BISSF_MACROS or BISSF_INCOMPLETE set
    if(!Flags){
        return NULL;
    }
    
    //Only one of BISSF_MACROS and BISSF_INCOMPLETE should be on at a time
    if((Flags & 0x3) == 3){
        return NULL;
    }
    
    //Declaring Buffers for code generation
    InstructionBuffer = calloc(0x4000, 1);
    JumpTable = calloc(0x2000, 1);
    ContextInfo = calloc(0x2000, 1);
    
    DeclBuffer = calloc(BufferSize*5, 1);
    
    //InstructionBuffer[0] = 0;
    //JumpTable[0] = 0;
    //ContextInfo[0] = 0;
    
    memset(InstructionsUsed, 0, 0x100);
    
    if(InitialContext){
        char* ContextString;
        
        ContextString = ECX_GenerateContextString(InitialContext);
        strcpy(ContextInfo, ContextString);
        
        free(ContextString);
    }    
    
    //Creating the JumpTable list
    JumpTableList = LST_New(NULL);
    LST_AttachRemovalRoutine(JumpTableList, JTD_Destroyer);
    
    //generate instruction function calls or macro expansions
    for(i = 0; i < Block->Length; i++){
        
        //if this is an incomplete run, check if the current instruction is
        //an entrypoint. If it is, we should finalize code generation
        if(Flags & BISSF_INCOMPLETE){
            //The first instruction is always a target. (i == 0)
            if(i){
                if(BMCSearchEntryPoint(Block, Block->Instructions[i]->Addr)){
                    strcat(InstructionBuffer, "\t//==> (EntryPoint) <==");
                    break;
                }
            }
        }         
        
        //Inserting label (Only needed of Jump Table is needed)
        if(Flags & BISSF_JUMPTABLE){
            sprintf(Scrap, "\t__%04X__:\n", Block->Instructions[i]->Addr);
            
            //Enqueue Jump Table Information
            JTData = JTD_New(Block->Instructions[i]->Addr, Block->Instructions[i]->Size);
            
            JTData->Size = CPUGetInstructionSize(Block->Instructions[i]->Opcode);
            
            LST_Enqueue(JumpTableList, JTData);            
        }
        else
            sprintf(Scrap, "\t/*__%04X__:*/\n", Block->Instructions[i]->Addr);
        strcat(InstructionBuffer, Scrap);
        
        if(Flags & BISSF_MACROS){
           
            if(Settings.ArgumentFlags & AF_OPT_IMMEDIATE){
                if(BIS_IsAddrModeStatic(IRSynthInfo[Block->Instructions[i]->Opcode].AddressingMode)){

                    sprintf(Scrap, "Value = 0x%02X;", Block->Instructions[i]->Operands[0]);
                    strcat(ContextInfo, Scrap);

                    //Check if the current instruction is one of the many that 
                    //use the LastEffectiveAddress "hack" on 6502.c
                    //If so, it should also be set as context information. CPU.LastEffectiveAddress = PC+1

                    if(IRSynthInfo[Block->Instructions[i]->Opcode].LastEffectiveAddressInstruction){
                        sprintf(Scrap, "CPU.LastEffectiveAddress = 0x%04X;", 
                                Block->Instructions[i]->Addr+1);
                        strcat(ContextInfo, Scrap);
                    }

                }
                else{
                    strcat(ContextInfo, "__STANDARD_CONTEXT__");
                }
            }
            else{
                strcat(ContextInfo, "__STANDARD_CONTEXT__");
            }
            
        }
        
        //Update PC
        //sprintf(Scrap, "\tCPU.PC = 0x%04X;\n", Block->Instructions[i]->Addr);
        //strcat(InstructionBuffer, Scrap);        

        //Update CurrentOpcode
        sprintf(Scrap, "\tCPU.CurrentOpcode = 0x%02X;\n", Block->Instructions[i]->Opcode);
        strcat(InstructionBuffer, Scrap);        
        
        //Create the function call or macro site depending on option
        if(Flags & BISSF_PRECOMPILED)
            sprintf(Scrap, "\t%s();", IRSynthInfo[Block->Instructions[i]->Opcode].InstructionPtr());
        else
            sprintf(Scrap, "\t%s(%s)", IRSynthInfo[Block->Instructions[i]->Opcode].InstructionPtr(), ContextInfo);          
        
        strcat(InstructionBuffer, Scrap);
        strcpy(ContextInfo, "\0");
        
        //Check if it is a branch instruction. If so, attach a target comment to facilitate debugging
        if(Block->Instructions[i]->InstructionType == BMCIT_Branch){
            sprintf(Scrap, " //Target: %04X", Block->Instructions[i]->InstructionInformation.BranchInfo->Target);
            strcat(InstructionBuffer, Scrap);
        }
        strcat(InstructionBuffer, "\n");
        
        //Cycle counting update
        sprintf(Scrap, "\tRemainingCycles -= %d;\n", IRSynthInfo[Block->Instructions[i]->Opcode].OpCycles);
        strcat(InstructionBuffer, Scrap);
        
        //Mark the current instruction as used
        InstructionsUsed[Block->Instructions[i]->Opcode] = 1;
        
        if(Block->Instructions[i]->InstructionType == BMCIT_Branch && (!(Flags & BISSF_INCOMPLETE))){
            if(Block->Instructions[i]->InstructionInformation.BranchInfo->BranchFlags & BMCBI_TargetInSameBMC){
                //strcat(InstructionBuffer, "\t_JUMP_PC\n\n");
                if((i + 1) >= Block->Length)
                    sprintf(Scrap, "\t_JUMP_PC_T(0x%04X, 0x%04X)\n\n", 
                            Block->StartAddr, Block->Instructions[i]->InstructionInformation.BranchInfo->Target);
                else
                    sprintf(Scrap, "\t_JUMP_PC(0x%04X)\n\n", Block->StartAddr);
                
                strcat(InstructionBuffer, Scrap);
                
            }
            else
                strcat(InstructionBuffer, "\treturn RemainingCycles;\n\n");
        }
        else{
            //Update PC
            if(Block->Instructions[i]->InstructionType != BMCIT_Jump){
                sprintf(Scrap, "\tCPU.PC = 0x%04X;\n", Block->Instructions[i]->Addr + CPUGetInstructionSize(Block->Instructions[i]->Opcode));
                strcat(InstructionBuffer, Scrap);
            }
        }
        
        if(IRSynthInfo[Block->Instructions[i]->Opcode].ScanData == SDE_HARDSPOT){
            //The current instruction is a hardspot (jumps and other unconditional branches)
            strcat(InstructionBuffer, "\n\treturn RemainingCycles;\n\n");
        }
        
    }

    //The last instruction is always a return that indicates how many remaining
    //cycles. This function returns to the execution engine so it can schedule
    //the next block to be run
    strcat(InstructionBuffer, "\n\treturn RemainingCycles;\n\n");    
    strcat(InstructionBuffer, "}\n\n");
    
    //Print some info
    sprintf(Scrap, "//Block size: %d\n//Pure: %d\n//Cycles: %d\n", Block->BlockSize, Block->Pure, Block->Cycles);
    strcat(DeclBuffer, Scrap);
    sprintf(Scrap, "//Generation flags: 0x%04X\n\n", Flags);
    strcat(DeclBuffer, Scrap);
    
    //Generate Macro definitions
    strcat(DeclBuffer, "#include <stdint.h>\n");
    strcat(DeclBuffer, "#include \\\"6502.h\\\"\n\n");
    
    //generate extern declarations
    strcat(DeclBuffer, "extern __Context CPU;\n");
    strcat(DeclBuffer, "extern const InstructionData Instructions[256];\n");
    strcat(DeclBuffer, "extern uint8_t (*AddressingModeDecoders[13])();\n\n");
    
    if(Flags & BISSF_MACROS)
        strcat(DeclBuffer, "#include \\\"6502_InstructionMacros.h\\\"\n\n");
    
    //Generate Macros
    //sprintf(Scrap, "#define _JUMP_PC \\\nif(RemainingCycles <= 0) \\\n\treturn RemainingCycles; \\\nOffset = CPU.PC - 0x%04X; \\\ngoto *JumpTable[Offset];\n\n", Block->StartAddr);
    //strcat(DeclBuffer, Scrap);
    
    strcat(DeclBuffer, "#include \\\"6502_DBTSupport.h\\\"\n\n");
    
    if(Flags & BISSF_MACROS)
        strcat(DeclBuffer, "#define __STANDARD_CONTEXT__ \\\n\tValue = AddressingModeDecoders[Instructions[CPU.CurrentOpcode].AddressingMode]();\n\n");
    
    
    //Generate instruction function declarations
    //This is not needed for now.
    /*for(i = 0; i < 0x100; i++){
        if(InstructionsUsed[i]){
            sprintf(Scrap, "inline void %s(void);\n" , IRSynthInfo[i].InstructionPtr());
            strcat(DeclBuffer, Scrap);
        }
    }*/
    
    //Generating jump table
    if(Flags & BISSF_JUMPTABLE){
        sprintf(JumpTable, "\tconst void* JumpTable[] = {");
        JTData = LST_Dequeue(JumpTableList);
        while(JTData){

            strcat(JumpTable, "\n\t\t");

            for(i = 0; i < JTData->Size; i++){
                sprintf(Scrap, "&&__%04X__, ", JTData->StartingAddress);
                strcat(JumpTable, Scrap);
            }

            free(JTData);

            JTData = LST_Dequeue(JumpTableList);
        }
        strcat(JumpTable, "\n\t};\n\n");
    }
    
    strcat(DeclBuffer, "\n");
    
    //if(Block->Pure)
        //strcat(DeclBuffer, "inline void __NES__SetContext__(void);\n\n");
    
    sprintf(Scrap, "uint32_t __NES__%04X__(int32_t RemainingCycles){\n\n", Block->StartAddr);
    strcat(DeclBuffer, Scrap);
    
    strcat(DeclBuffer, JumpTable);
    
    //Extra variables needed during execution
    strcat(DeclBuffer, "\tuint16_t Offset;\n\n");
    
    if(Flags & BISSF_MACROS){
        strcat(DeclBuffer, "\tuint8_t  Value = 0;\n");
        strcat(DeclBuffer, "\tuint8_t  H = 0;\n");
        strcat(DeclBuffer, "\tuint8_t  L = 0;\n");
        strcat(DeclBuffer, "\tuint8_t  Temp8 = 0;\n");
        strcat(DeclBuffer, "\tuint16_t Temp16 = 0;\n");
        strcat(DeclBuffer, "\tuint32_t Temp32 = 0;\n\n");
    }
    
    //if(Block->Pure)
    //    strcat(DeclBuffer, "\t__NES__SetContext__();\n\n");
    //else
    //    strcat(DeclBuffer, "\t_JUMP_PC\n\n");
    
    if(!(Flags & BISSF_INCOMPLETE)){
        //strcat(DeclBuffer, "\t_JUMP_PC\n\n");
        sprintf(Scrap, "\t_JUMP_PC(0x%04X)\n\n", Block->StartAddr);
        strcat(DeclBuffer, Scrap);        
    }
    
    strcat(DeclBuffer, InstructionBuffer); 
    
    //printf(DeclBuffer);
    
    free(InstructionBuffer);
    free(JumpTable);
    free(JumpTableList);
    free(ContextInfo);
    
    return DeclBuffer;
}
